﻿// 2023/3/8: 首个版本
// 2023/4/21: 修正XML节点内容显示
// 2023/4/23: 修正选择报文/信号链接无法启用问题
// 2023/4/27: 支持按base64编辑
// 2023/9/25: 改为直接输入Icon对象

using System;
using System.Xml;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Eto.Forms;
using Eto.Drawing;
using ASEva;
using ASEva.Samples;
using ASEva.UIEto;

namespace CommonSource
{
    class XmlEditor : DialogPanel
    {
        public XmlEditor(String xmlString, Icon icon = null)
        {
            Icon = icon;
            Title = chinese ? "XML编辑器" : "XML Editor";
            SetResizableMode(800, 600, 800, 600);

            var mainLayout = this.SetContentAsColumnLayout();
            var barLayout = mainLayout.AddRowLayout();
            var bodyLayout = mainLayout.AddRowLayout(true, 8, VerticalAlignment.Stretch);
            var tailLayout = mainLayout.AddRowLayout();

            barLayout.AddLabel(chinese ? "父节点:" : "Parent node:");
            labelParent = barLayout.AddLabel("-", true);

            var leftSideLayout = bodyLayout.AddColumnLayout(false, 250, 0, 8);
            var mainNodesLayout = leftSideLayout.AddColumnLayout(true, 2);
            var childNodesLayout = leftSideLayout.AddColumnLayout(false, 0, 150, 2);

            var mainElemsTitleLayout = mainNodesLayout.AddRowLayout();
            mainList = mainNodesLayout.AddControl(new ListBox(), true) as ListBox;

            mainElemsTitleLayout.AddLabel(chinese ? "节点:" : "Nodes:");
            mainElemsTitleLayout.AddSpace();
            linkRemove = mainElemsTitleLayout.AddLinkButton(chinese ? "移除" : "Remove");
            linkDuplicate = mainElemsTitleLayout.AddLinkButton(chinese ? "复制" : "Duplicate");
            linkGoBack = mainElemsTitleLayout.AddLinkButton(chinese ? "返回上层" : "Go back");
            
            childNodesLayout.AddLabel(chinese ? "子节点:" : "Child nodes:");
            childList = childNodesLayout.AddControl(new ListBox(), true) as ListBox;

            var rightSideLayout = bodyLayout.AddColumnLayout(true);
            var attribsLayout = rightSideLayout.AddColumnLayout(true, 2);
            var contentLayout = rightSideLayout.AddColumnLayout(false, 0, 150, 2);

            var attribsTitleLayout = attribsLayout.AddRowLayout();
            textTable = attribsLayout.AddControl(new TextTableView(), true) as TextTableView;
            textTable.AddColumn(chinese ? "键" : "Key", 250, false);
            textTable.AddColumn(chinese ? "值" : "Value", 250, true);

            attribsTitleLayout.AddLabel(chinese ? "节点属性:" : "Node attributes:");
            attribsTitleLayout.AddSpace();
            linkEditBase64Attrib = attribsTitleLayout.AddLinkButton(chinese ? "编辑Base64" : "Edit Base64");
            linkSelectSignal = attribsTitleLayout.AddLinkButton(chinese ? "选择信号" : "Select signal");
            linkSelectMessage = attribsTitleLayout.AddLinkButton(chinese ? "选择报文" : "Select message");

            var contentTitleLayout = contentLayout.AddRowLayout();
            textArea = contentLayout.AddControl(new TextArea(), true) as TextArea;

            contentTitleLayout.AddLabel(chinese ? "节点文本:" : "Node content:");
            contentTitleLayout.AddSpace();
            linkEditBase64Content = contentTitleLayout.AddLinkButton(chinese ? "编辑Base64" : "Edit Base64");

            tailLayout.AddSpace();
            tailLayout.AddButton(chinese ? "取消" : "Cancel", false, 60).Click += delegate { this.Close(); };
            tailLayout.AddButton(chinese ? "确定" : "OK", false, 60).Click += delegate
            {
                BoolResult = true;
                this.Close();
            };

            xml = new XmlDocument();
            xml.LoadXml(xmlString);
            parentNode = null;
            currentNode = xml.DocumentElement;
            updateUI(true);

            linkGoBack.Click += delegate
            {
                if (parentNode != null)
                {
                    currentNode = parentNode;
                    if (parentNode.Equals(xml.DocumentElement)) parentNode = null;
                    else parentNode = parentNode.ParentNode as XmlElement;
                    updateUI(true);
                }
            };

            linkRemove.Click += delegate
            {
                if (parentNode != null && mainList.SelectedIndex >= 0)
                {
                    currentNode = null;
                    parentNode.RemoveChild(GetChildElements(parentNode)[mainList.SelectedIndex]);
                    updateUI(true);
                }
            };

            linkDuplicate.Click += delegate
            {
                if (parentNode != null && mainList.SelectedIndex >= 0)
                {
                    var newNode = GetChildElements(parentNode)[mainList.SelectedIndex].CloneNode(true);
                    parentNode.AppendChild(newNode);
                    currentNode = newNode as XmlElement;
                    updateUI(true);
                }
            };

            textTable.CellEdited += (sender, args) =>
            {
                if (updatingUI) return;
                currentNode.Attributes[args.Row].Value = textTable.GetValue(args.Row, args.Column);
            };

            textTable.SelectedRowsChanged += delegate
            {
                linkEditBase64Attrib.Enabled = linkSelectMessage.Enabled = linkSelectSignal.Enabled = textTable.SelectedRow >= 0;
            };

            linkSelectSignal.Click += delegate
            {
                var selectedRow = textTable.SelectedRow;
                if (selectedRow < 0) return;
                var curValue = textTable.GetValue(selectedRow, 1);
                if (curValue != null && curValue.Split(':', StringSplitOptions.RemoveEmptyEntries).Length != 3) curValue = null;
                var config = Agency.SelectSignal(new SignalConfig{ SignalID = curValue }, false, false, null);
                if (config != null && config.SignalID != null)
                {
                    textTable.SetValue(selectedRow, 1, config.SignalID);
                    currentNode.Attributes[selectedRow].Value = config.SignalID;
                }
            };

            linkSelectMessage.Click += delegate
            {
                var selectedRow = textTable.SelectedRow;
                if (selectedRow < 0) return;
                var curValue = textTable.GetValue(selectedRow, 1);
                if (curValue != null && curValue.Split(':', StringSplitOptions.RemoveEmptyEntries).Length != 2) curValue = null;
                var message = Agency.SelectBusMessage(curValue);
                if (message != null)
                {
                    textTable.SetValue(selectedRow, 1, message);
                    currentNode.Attributes[selectedRow].Value = message;
                }
            };

            linkEditBase64Attrib.Click += delegate
            {
                var selectedRow = textTable.SelectedRow;
                if (selectedRow < 0) return;
                var curValue = textTable.GetValue(selectedRow, 1);
                var dialog = new Base64Editor(curValue, icon);
                App.RunDialog(dialog);
                if (dialog.BoolResult)
                {
                    var newValue = dialog.StringResult;
                    textTable.SetValue(selectedRow, 1, newValue);
                    currentNode.Attributes[selectedRow].Value = newValue;
                }
            };

            linkEditBase64Content.Click += delegate
            {
                var curValue = textArea.Text;
                var dialog = new Base64Editor(curValue, icon);
                App.RunDialog(dialog);
                if (dialog.BoolResult)
                {
                    var newValue = dialog.StringResult;
                    textArea.Text = newValue;
                }
            };

            mainList.SelectedIndexChanged += delegate
            {
                if (updatingUI) return;
                if (mainList.SelectedIndex < 0) currentNode = null;
                else currentNode = parentNode == null ? xml.DocumentElement : GetChildElements(parentNode)[mainList.SelectedIndex] as XmlElement;
                updateUI(false);
            };

            mainList.Activated += delegate
            {
                if (mainList.SelectedIndex < 0) return;
                var targetParentNode = parentNode == null ? xml.DocumentElement : GetChildElements(parentNode)[mainList.SelectedIndex] as XmlElement;
                if (!HasChildElements(targetParentNode)) return;
                parentNode = targetParentNode;
                currentNode = GetChildElements(parentNode)[0] as XmlElement;
                updateUI(true);
            };

            childList.Activated += delegate
            {
                if (mainList.SelectedIndex < 0 || childList.SelectedIndex < 0) return;
                var targetParentNode = parentNode == null ? xml.DocumentElement : GetChildElements(parentNode)[mainList.SelectedIndex] as XmlElement;
                if (!HasChildElements(targetParentNode)) return;
                parentNode = targetParentNode;
                currentNode = GetChildElements(parentNode)[childList.SelectedIndex] as XmlElement;
                updateUI(true);
            };

            textArea.TextChanged += delegate
            {
                if (updatingUI) return;
                if (currentNode != null && GetChildElements(currentNode).Length == 0) currentNode.InnerText = textArea.Text;
            };
        }

        public String XmlString
        {
            get
            {
                return ToNiceString(xml);
            }
        }

        private void updateUI(bool fullUpdate) // false: parentNode未变，仅当前选中节点变化
        {
            updatingUI = true;

            // parent label
            if (fullUpdate)
            {
                if (parentNode == null)
                {
                    labelParent.Text = chinese ? "(无)" : "(None)";
                }
                else
                {
                    var tags = new List<String>();
                    XmlNode node = parentNode;
                    while (node != null && node is XmlElement)
                    {
                        tags.Add(node.Name);
                        node = node.ParentNode;
                    }
                    tags.Reverse();
                    labelParent.Text = String.Join(" > ", tags);
                }
            }

            // main list
            if (fullUpdate)
            {
                mainList.Items.Clear();
                bool found = false;
                if (parentNode == null)
                {
                    mainList.Items.Add(xml.DocumentElement.Name);
                }
                else
                {
                    foreach (XmlElement node in GetChildElements(parentNode))
                    {
                        mainList.Items.Add(node.Name);
                        if (currentNode != null && currentNode.Equals(node)) 
                        {
                            mainList.SelectedIndex = mainList.Items.Count - 1;
                            found = true;
                        }
                    }
                }
                if (!found && mainList.Items.Count > 0)
                {
                    mainList.SelectedIndex = 0;
                }
            }

            // main list buttons
            linkRemove.Enabled = parentNode != null && mainList.SelectedIndex >= 0;
            linkDuplicate.Enabled = parentNode != null && mainList.SelectedIndex >= 0;
            linkGoBack.Enabled = parentNode != null;

            // child list
            bool withChildren = false;
            childList.Items.Clear();
            if (currentNode != null)
            {
                foreach (XmlElement node in GetChildElements(currentNode))
                {
                    childList.Items.Add(node.Name);
                    withChildren = true;
                }
            }

            // text table
            textTable.RemoveAllRows();
            if (currentNode != null)
            {
                foreach (XmlAttribute attrib in currentNode.Attributes)
                {
                    textTable.AddRow(new String[] { attrib.Name, attrib.Value });
                }
            }

            // text table buttons
            linkEditBase64Attrib.Enabled = linkSelectSignal.Enabled = linkSelectMessage.Enabled = false;

            // text area and buttons
            if (currentNode == null || withChildren)
            {
                textArea.Enabled = false;
                textArea.Text = "";
                linkEditBase64Content.Enabled = false;
            }
            else
            {
                textArea.Text = currentNode.InnerText;
                textArea.Enabled = true;
                linkEditBase64Content.Enabled = true;
            }

            updatingUI = false;
        }

        private bool HasChildElements(XmlElement node)
        {
            foreach (var subNode in node.ChildNodes)
            {
                if (subNode is XmlElement) return true;
            }
            return false;
        }

        private XmlElement[] GetChildElements(XmlElement node)
        {
            var elems = new List<XmlElement>();
            foreach (var subNode in node.ChildNodes)
            {
                if (subNode is XmlElement) elems.Add(subNode as XmlElement);
            }
            return elems.ToArray();
        }

        private String ToNiceString(XmlDocument xml)
        {
            if (xml == null) return "";

            var memoryStream = new MemoryStream();

            var settings = new XmlWriterSettings();
            settings.Indent = true;
            settings.IndentChars = "    ";

            var xmlWriter = XmlWriter.Create(memoryStream, settings);
            xml.Save(xmlWriter);
            xmlWriter.Close();

            memoryStream.Position = 3; // BOM
            var data = new byte[memoryStream.Length - 3];
            memoryStream.Read(data, 0, (int)memoryStream.Length - 3);
            memoryStream.Close();

            return Encoding.UTF8.GetString(data);
        }

        private XmlDocument xml;
        private XmlElement parentNode, currentNode;
        private bool updatingUI = false;
        private bool chinese = Agency.GetAppLanguage() == "ch";

        private Label labelParent;
        private ListBox mainList, childList;
        private TextTableView textTable;
        private TextArea textArea;
        private LinkButton linkRemove, linkDuplicate, linkGoBack, linkSelectSignal, linkSelectMessage, linkEditBase64Attrib, linkEditBase64Content;
    }

    class Base64Editor : DialogPanel
    {
        public Base64Editor(String base64String, Icon icon = null)
        {
            Icon = icon;
            Title = chinese ? "Base64编辑器" : "Base64 Editor";
            SetResizableMode(600, 400, 600, 400);

            var mainLayout = this.SetContentAsColumnLayout();
            var textArea = mainLayout.AddControl(new TextArea(), true) as TextArea;
            var buttonsLayout = mainLayout.AddRowLayout();
            buttonsLayout.AddSpace();
            var buttonCancel = buttonsLayout.AddButton(chinese ? "取消" : "Cancel", false, 80);
            var buttonOK = buttonsLayout.AddButton(chinese ? "确定" : "OK", false, 80);

            String text = "";
            try { text = Encoding.UTF8.GetString(Convert.FromBase64String(base64String)); }
            catch (Exception) {}
            textArea.Text = text;

            buttonOK.Click += delegate
            {
                StringResult = Convert.ToBase64String(Encoding.UTF8.GetBytes(textArea.Text));
                BoolResult = true;
                Close();
            };
            buttonCancel.Click += delegate
            {
                Close();
            };
        }

        private bool chinese = Agency.GetAppLanguage() == "ch";
    }
}